package com.demo.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.demo.entity.SignInInfoEntity;
import com.demo.mapper.SignInMapper;
import com.demo.mapper.StudentMapper;
import com.demo.util.QRCodeGenerator;
import com.demo.util.Redis;
import com.google.zxing.WriterException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import redis.clients.jedis.Jedis;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/wx")
public class SignInController {
    private Log log = LogFactory.getLog(this.getClass());

    private static final String APPID = "wx9b04d88d2bf7f251";

    private static final String APPSECRET = "8b79027ddf3f0d229c075d7301a219d4";

    @Autowired
    private RestTemplate restTemplate;

    @Resource
    private SignInMapper signInMapper;

    @Resource
    private StudentMapper studentMapper;

    Jedis redis = Redis.getRedis();



    /**
     * 小程序获取微信用户唯一openid
     *
     * @param code 小程序临时code凭证
     * @return Map
     */
    @PostMapping("/login")
    public Object wxLogin(String code) {
        // 获取openid和sessionKey
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + APPID + "&secret=" + APPSECRET + "&js_code=" + code + "&grant_type=authorization_code";
        String postForObject = restTemplate.postForObject(url, null, String.class);
        // 提取openid和sessionKey
        JSONObject jsonObject = JSONObject.parseObject(postForObject);
        String openid = jsonObject.getString("openid");
        // 自定义sessionToken，将openid和sessionKey关联
        String sessionToken = DigestUtils.md5Hex(openid);

        // 检查用户是否存在
        Map map = null;
        map = signInMapper.checkWxUserIsExists(openid);

        if (map != null) {
            map.remove("openId");
            map.put("userIsExists", true);
        } else {
            map = new HashMap();
            map.put("userIsExists", false);
        }

        map.put("sessionToken", sessionToken);
        if (redis.exists("sessionToken:" + sessionToken)) {
            Long ttl = redis.ttl("sessionToken:" + sessionToken);
            map.put("expireTime", DateTime.now().offset(DateField.SECOND, Math.toIntExact(ttl)).toString());
        } else {
            map.put("expireTime", DateTime.now().offset(DateField.HOUR, 2).toString());
            // 缓存sessionToken，设置过期时间为2小时
            redis.setex("sessionToken:" + sessionToken, 60 * 60 * 2, jsonObject.toJSONString());
        }
        return map;
    }


    /**
     * 添加用户绑定信息
     *
     * @param sessionToken key-value value：openid，sessionkey
     * @param stuId        学号
     * @param stuName      姓名
     * @return success
     */
    @PostMapping("/addUserInfo")
    public String addUserInfo(String sessionToken, String stuId, String stuName, String nickName, String avatarUrl) {
        if (!redis.exists("sessionToken:" + sessionToken)) {
            return "sessionToken Expired";
        }
        String openid = JSONObject.parseObject(redis.get("sessionToken:" + sessionToken)).getString("openid");
        // 在插入姓名学号前，需要验证用户是否存在
        // 获取学生班级
        String stuClass = stuId.substring(0, 7);
        Map classStuIsExists = null;
        classStuIsExists = studentMapper.checkClassStuIsExists(stuClass, stuId, stuName);
        if (classStuIsExists == null) {
            return "user does not exist";
        }
        try {
            int addOpenId = signInMapper.addOpenId(openid, stuId, stuName, nickName, avatarUrl);
        } catch (Exception e) {
            return "user does not exist";
        }
        return "success";
    }

    /**
     * 获取签到码base64编码的图片接口
     *
     * @param stuClass 班级
     * @param second   间隔时间
     * @return 图片
     */
    @PostMapping("/getQRCode")
    public Object getQRCode(String stuClass, int second, String teacherNumber, String signInTimeLen, String courseName) {
        if (StringUtils.isEmpty(stuClass.trim())) return null;
        SecureRandom secureRandom = null;
        try {
            secureRandom = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        /**
         * 二维码内的信息[班级号，签到码，教师编号，签到凭证, 签到结束时间]
         * "{"classNumber":"2006831","infoSeal":"5d3f80b3be52bf02b21b986d1a02f9cf","signInCode":"1455323177","signInEndTime":"2021-9-29 15:15:19","signInId":"744825808","teacherNumber":"2006831517"}"
         */
        String signInCode = String.valueOf(Math.abs(secureRandom.nextInt()));
        // 生成本次签到唯一凭证，并设置过期时间为本次签到时间。防止重复签到
        String signInID = String.valueOf(Math.abs(secureRandom.nextInt()));
        // 这个课程名可能是中文，需要转一下码
        courseName = Base64.encode(courseName.getBytes());
        SignInInfoEntity signInInfoEntity = new SignInInfoEntity(stuClass, teacherNumber, signInCode, signInID, signInTimeLen, courseName, null);
        signInInfoEntity.setInfoSeal(DigestUtils.md5Hex(JSON.toJSONString(signInInfoEntity)));

        byte[] qrcode = null;
        try {
            qrcode = QRCodeGenerator.getQRCodeImage(JSON.toJSONString(signInInfoEntity), 360, 360);
        } catch (WriterException e) {
            System.out.println("Could not generate QR Code, WriterException :: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Could not generate QR Code, IOException :: " + e.getMessage());
        }
        /**
         * 以下方式为返回base63编码图片，可在前面img标签中显示
         */
        BASE64Encoder encoder = new BASE64Encoder();
        String data = "data:image/jpg;base64," + encoder.encode(qrcode);
        redis.setex("signInCode:" + signInCode, second, "签到码");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("data", data);
        jsonObject.put("signInCode", signInCode);
        return jsonObject;

        /**
         * 以下方式为ResponseEntity返回方式，前面访问直接显示在页面上
         */
        //final HttpHeaders headers = new HttpHeaders();
        //headers.setContentType(MediaType.IMAGE_PNG);
        //return new ResponseEntity<byte[]>(qrcode, headers, HttpStatus.CREATED);

    }

    /**
     * 学生扫码签到接口
     *
     * @param signInInfo   签到信息
     * @param sessionToken 用户token信息
     * @return success
     * <p>
     * https://apidoc.gitee.com/dromara/hutool/
     * hutool使用手册
     */

    //签到码错误
    private static final int SIGNINERROR = 43000;
    //验证码过期
    private static final int CODEEXPIRE = 43001;
    //重复签到
    private static final int REPEATSIGNIN = 43002;
    //用户令牌过期
    private static final int SESSIONTOKENEXPIRE = 43003;
    //用户签到成功
    private static final int SIGNINSUCCESS = 43004;
    //非法用户签到
    private static final int ILLEGALUSERSIGNIN = 43005;

    @PostMapping("/stuSignIn")
    public Object stuSignIn(String signInInfo, String sessionToken) {
        SignInInfoEntity signInInfoEntity = JSONObject.parseObject(signInInfo, SignInInfoEntity.class);
        if (this.checkSignIn_infoSeal(signInInfoEntity)) return SIGNINERROR;
        if (!redis.exists("signInCode:" + signInInfoEntity.getSignInCode())) return CODEEXPIRE;
        JSONObject jsonSessionToken = JSONObject.parseObject(sessionToken);
        sessionToken = jsonSessionToken.getString("sessionToken");
        if (redis.exists(signInInfoEntity.getSignInId() + ":" + sessionToken)) return REPEATSIGNIN;
        if (!redis.exists("sessionToken:" + sessionToken)) return SESSIONTOKENEXPIRE;

        signInInfoEntity.setCourseName(Base64.decodeStr(signInInfoEntity.getCourseName()));
        if(checkSignIn_illegalUserSignIn(signInInfoEntity.getClassNumber(), jsonSessionToken.getString("stuId"))) return ILLEGALUSERSIGNIN;

        StringBuffer str = new StringBuffer();
        str.append(signInInfoEntity.getTeacherNumber());
        str.append(signInInfoEntity.getClassNumber());
        str.append(signInInfoEntity.getCourseName());
        String md5Hex = DigestUtils.md5Hex(str.toString());
        if(redis.keys(md5Hex+"*").size()==0){
            signInMapper.addSignInRecord(md5Hex, signInInfoEntity.getTeacherNumber(), signInInfoEntity.getClassNumber(), signInInfoEntity.getCourseName());
        }

        String token = jsonSessionToken.getString("sessionToken");
        String openid = JSONObject.parseObject(redis.get("sessionToken:" + token)).getString("openid");

        StringBuffer key = new StringBuffer();
        key.append(md5Hex).append(":")
                .append(openid).append(":")
                .append(DateTime.now().year()).append(":").append(DateTime.now().month() + 1);

        String currDay = StrUtil.toString(DateTime.now().dayOfMonth());
        String hget = redis.hget(key.toString(), currDay);
        if (hget == null) {
            redis.hset(key.toString(), currDay, String.valueOf(1));
        } else {
            hget = StrUtil.toString(Integer.valueOf(hget) + 1);
            redis.hset(key.toString(), currDay, hget);
        }
        // 计算时间差，返回秒数。前端签到结束时间 - 当前系统时间 = 返回秒数。用户签到后会产生本次签到凭证，这是给凭证过期用的
        Date endTime = DateUtil.parse(signInInfoEntity.getSignInEndTime()).toJdkDate();
        long between = DateUtil.between(new Date(), endTime, DateUnit.SECOND);
        redis.setex(signInInfoEntity.getSignInId() + ":" + sessionToken, (int) between, "该用户已签");
        return SIGNINSUCCESS;
    }


    /**
     * 2006831517:2006831:o2MiL5Sa-LOp9Kc8bBYGnamqBBo0:2021:9
     * 2006831517 2006831 课程名 → hashcode
     */

    /**
     * 检查非法用户签到
     *
     * @param classNumber 班级号
     * @param stuId       学号
     * @return true/false
     */
    private boolean checkSignIn_illegalUserSignIn(String classNumber, String stuId) {
        if (classNumber.equals(stuId.substring(0, 7))) return false;
        return true;
    }

    /**
     * 检查签到信息--比对hash码
     *
     * @param signInInfoEntity 实体
     * @return true/false
     */
    private boolean checkSignIn_infoSeal(SignInInfoEntity signInInfoEntity) {
        String infoSeal = signInInfoEntity.getInfoSeal();
        signInInfoEntity.setInfoSeal(null);
        String md5Hex = DigestUtils.md5Hex(JSON.toJSONString(signInInfoEntity));
        if (infoSeal.equals(md5Hex)) return false;
        return true;
    }

    /**
     * 结束签到
     * @param signInCode 签到码
     */
    @GetMapping("/endSignIn")
    private void endSignIn(String signInCode){
        redis.del("signInCode:"+signInCode);
    }


}
